using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using Microsoft.CSharp;
using System.IO;
using System.CodeDom.Compiler;

using NBusiness.CodeDom;
using NBusiness.Data;
using System.Data.Common;
using System.Reflection;
using NBusiness.Templates.Contracts;
using NBusiness.Properties;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp.Compiler;
using Microsoft.VisualBasic;
using NBusiness.Utilities;
using NBusiness.Query;

namespace NBusiness.Templates
{
    [ResourceDescription("EntityBaseCollectionDescription")]
    public class EntityBaseCollectionTemplate : TemplateBase, IChildRelationship
	{
		#region Constructor
		public EntityBaseCollectionTemplate()
			: base()
		{ }
		#endregion

		#region Generate
        public override EntityResourceFile[] Generate(PartialLanguage partialLanguage, params Entity[] entities)
        {
            List<EntityResourceFile> files = new List<EntityResourceFile>();
            foreach (Entity entity in entities)
            {
                CodeCompileUnit unit = new CodeCompileUnit();
                CodeNamespace n = new CodeNamespace((entity.Parent as IFamilyUnit).Fullname);
                string fileName = "EntityCollectionBase\\" + entity.Fullname + "Collection." + partialLanguage.ToString().ToLower();

                n.Comments.Add(new CodeCommentStatement(fileName + " - '" + this.GetType().FullName + "'"));
                n.Comments.Add(new CodeCommentStatement("This file was automatically generated by NBusiness"));
                n.Comments.Add(new CodeCommentStatement("on " + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToLongTimeString()));

                n.Imports.Add(new CodeNamespaceImport("System"));
                n.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
                n.Imports.Add(new CodeNamespaceImport("System.Data.Common"));
                n.Imports.Add(new CodeNamespaceImport("NBusiness.Data"));
                n.Imports.Add(new CodeNamespaceImport("NBusiness.Query"));
                unit.Namespaces.Add(n);

                StringWriter sw = new StringWriter();
                CodeTypeDeclaration d = CreateMainClass(entity);
                if (d != null)
                {
                    CreateConstructors(entity, d);
                    CreateCriteria(entity, d);
                    CreateFactoryMethods(entity, d);
                    CreateDataAccess(entity, d);
                    CreateLoad(entity, d);
                    CreateToInfoArray(entity, d);
                    n.Types.Add(d);
                   
                    CodeGeneratorOptions op = new CodeGeneratorOptions();
                    CodeDomProvider provider = null;
                    switch (partialLanguage)
                    {
                        case PartialLanguage.CS:
                            provider = new CSharpCodeProvider();
                            op.BracingStyle = "C";
                            op.ElseOnClosing = false;
                            break;
                        case PartialLanguage.VB:
                            provider = new VBCodeProvider();
                            break;
                    }
                    op.BlankLinesBetweenMembers = false;
                    op.IndentString = "\t";
                    op.VerbatimOrder = true;
                    provider.GenerateCodeFromCompileUnit(unit, sw, op);
                }

                string code = sw.GetStringBuilder().ToString();
                byte[] data = Encoding.UTF8.GetBytes(code);

                EntityResourceFile file = new EntityResourceFile(
                    fileName,
                    EntityBuildAction.Compile);
                file.Data = data;

                files.Add(file);
            }
            return files.ToArray();
        }
		#endregion

		#region Create Main Class
		protected virtual CodeTypeDeclaration CreateMainClass(Entity entity)
		{
            CodeTypeDeclaration d = null;

            if (Dependency.GetContract<IEntityBase>(entity) != null)
            {
                d = new CodeTypeDeclaration(entity.Name + "Collection");
                d.BaseTypes.Add(new CodeTypeReference(
                    "EntityBaseCollection",
                    new CodeTypeReference(entity.Name)));

                d.IsClass = true;
                d.IsPartial = true;
                d.Attributes = MemberAttributes.Public;
                d.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(SerializableAttribute))));
            }
            else
            {
                EntityElement declaration = entity.Elements.Find(new Predicate<EntityElement>(delegate(EntityElement el)
                {
                    return el.Type == EntityElementType.EntityDeclaration;
                }));

                OnWarning(new ESharpBuildWarning(
                    ESharpErrorCode.ES0007,
                    declaration));
            }

			return d;
		}
		#endregion

		#region Create Constructors
        /// <summary>
        /// Calls all constructor creation methods.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="declaration"></param>
		protected virtual void CreateConstructors(Entity entity, CodeTypeDeclaration declaration)
        {
            CreateParameterlessConstructor(entity, declaration);
            CreateDefaultConstructor(entity, declaration);
            CreateQueryConstructor(entity, declaration);
        }

        protected virtual void CreateQueryConstructor(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeConstructor c = new CodeConstructor();
            c.Name = entity.ToString();
            c.Attributes = MemberAttributes.Assembly;
            c.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IQuery), "query"));
            CodeParameterDeclarationExpression parameters = new CodeParameterDeclarationExpression(typeof(Parameter[]), "parameters");
            parameters.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(ParamArrayAttribute))));
            c.Parameters.Add(parameters);

            CodeObjectCreateExpression criteria = new CodeObjectCreateExpression(
                typeof(QueryCriteria),
                new CodeVariableReferenceExpression("query"),
                new CodeVariableReferenceExpression("parameters"));

            c.BaseConstructorArgs.Add(criteria);
            declaration.Members.Add(c);
        }
        /// <summary>
        /// Creates a public parameterless constructor.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="declaration"></param>
        protected virtual void CreateParameterlessConstructor(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeConstructor c = new CodeConstructor();
            c.Name = entity.ToString();
            c.Attributes = MemberAttributes.Public;
            c.BaseConstructorArgs.Add(new CodeSnippetExpression(""));

            declaration.Members.Add(c);
        }
        /// <summary>
        /// Creates a private constructor that accepts a criteria object as a parameter.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="declaration"></param>
        protected virtual void CreateDefaultConstructor(Entity entity, CodeTypeDeclaration declaration)
		{
            CodeConstructor c = new CodeConstructor();
            c.Name = entity.ToString();
            c.Attributes = MemberAttributes.Private;
            c.Parameters.Add(new CodeParameterDeclarationExpression(typeof(CriteriaBase), "c"));
            c.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("c"));
            
            declaration.Members.Add(c);
        }
		#endregion

		#region Create Factory Methods
		protected virtual void CreateFactoryMethods(Entity entity, CodeTypeDeclaration declaration)
		{
            CreateFetchAll(entity, declaration);
            CreateRelationshipFactories(entity, declaration);
            CreateQuery(entity, declaration);
		}

		#region CreateRelationshipFactories
		private void CreateRelationshipFactories(Entity entity, CodeTypeDeclaration declaration)
		{
            foreach (EntityRelationship relationship in entity.Relationships)
			{
                if (relationship.With == entity && relationship.Type == EntityRelationshipType.Child)
				{
                    IParentRelationship parent = Dependency.GetContract<IParentRelationship>(relationship.From);
                    if (parent != null)
                    {
                        CodeMemberMethod[] members = parent.FetchByParentMethods(relationship);
                        declaration.Members.AddRange(members);
                    }
                    else
                    {
                        OnWarning(new ESharpBuildWarning(
                            ESharpErrorCode.ES0004,
                            entity.Elements[0]));
                    }
				}
			}
		} 
		#endregion

		#region Create FetchAll
		protected virtual void CreateFetchAll(Entity entity, CodeTypeDeclaration declaration)
		{
			CodeMemberMethod m = new CodeMemberMethod();
			m.Name = "FetchAll";
			m.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m.ReturnType = new CodeTypeReference(entity.Name + "Collection");
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeObjectCreateExpression fetchAllCriteria = new CodeObjectCreateExpression("FetchAllCriteria");
            CodeObjectCreateExpression newCollection = new CodeObjectCreateExpression(entity.Name + "Collection", fetchAllCriteria);
            CodeVariableDeclarationStatement Collection = new CodeVariableDeclarationStatement(
                entity.Name + "Collection",
                "items",
                newCollection);
            m.Statements.Add(Collection);

            CodeMethodReturnStatement ret = new CodeMethodReturnStatement(
                new CodeVariableReferenceExpression("items"));
            m.Statements.Add(ret);
            
            CodeMemberMethod m2 = new CodeMemberMethod();
            m2.Name = "FetchAll";
            m2.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m2.ReturnType = new CodeTypeReference(entity.Name + "Collection");
            m2.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m2.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m2);

            CodeVariableDeclarationStatement totalEntities = new CodeVariableDeclarationStatement(typeof(int), "totalEntities");
            fetchAllCriteria = new CodeObjectCreateExpression("FetchAllCriteria");
            CodeVariableDeclarationStatement criteria = new CodeVariableDeclarationStatement(
                "FetchAllCriteria", "criteria", fetchAllCriteria);
            m2.Statements.Add(criteria);
            newCollection = new CodeObjectCreateExpression(entity.Name + "Collection", new CodeVariableReferenceExpression("criteria"));
            Collection = new CodeVariableDeclarationStatement(
                entity.Name + "Collection",
                "items",
                newCollection);
            CodeAssignStatement assignTotal = new CodeAssignStatement(
                new CodeVariableReferenceExpression("totalEntities"),
                new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("criteria"), "TotalEntities"));
            m2.Statements.Add(Collection);
            m2.Statements.Add(assignTotal);           
            
            CodeVariableReferenceExpression startIndex = new CodeVariableReferenceExpression("startIndex");
            CodeVariableReferenceExpression maxRows = new CodeVariableReferenceExpression("maxRows");
            CodeVariableReferenceExpression sortField = new CodeVariableReferenceExpression("sortField");
            CodeVariableReferenceExpression sortOrder = new CodeVariableReferenceExpression("sortOrder");

            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "startIndex"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "maxRows"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sortField"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(SortOrder), "sortOrder"));
            CodeParameterDeclarationExpression totalOut = new CodeParameterDeclarationExpression(typeof(int), "totalEntities");
            totalOut.Direction = FieldDirection.Out;
            m2.Parameters.Add(totalOut);

            ret = new CodeMethodReturnStatement(new CodeVariableReferenceExpression("items"));
            m2.Statements.Add(ret);

            fetchAllCriteria.Parameters.Add(startIndex);
            fetchAllCriteria.Parameters.Add(maxRows);
            fetchAllCriteria.Parameters.Add(sortField);
            fetchAllCriteria.Parameters.Add(sortOrder);            
		}
		#endregion

        #region Create Query
        protected virtual void CreateQuery(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = "Query";
            m.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m.ReturnType = new CodeTypeReference(entity.Name + "Collection");
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IQuery), "query"));

            CodeParameterDeclarationExpression parametersDeclaration = 
                new CodeParameterDeclarationExpression(typeof(Parameter[]), "parameters");
            parametersDeclaration.CustomAttributes.Add(new CodeAttributeDeclaration("System.ParamArrayAttribute"));
            m.Parameters.Add(parametersDeclaration);
            declaration.Members.Add(m);

            CodeObjectCreateExpression queryCriteria = new CodeObjectCreateExpression(
                "QueryCriteria",
                new CodeVariableReferenceExpression("query"),
                new CodeVariableReferenceExpression("parameters"));

            CodeObjectCreateExpression newCollection = new CodeObjectCreateExpression(
                entity.Name + "Collection", 
                queryCriteria);
            CodeVariableDeclarationStatement Collection = new CodeVariableDeclarationStatement(
                entity.Name + "Collection",
                "items",
                newCollection);
            m.Statements.Add(Collection);

            CodeMethodReturnStatement ret = new CodeMethodReturnStatement(
                new CodeVariableReferenceExpression("items"));
            m.Statements.Add(ret);
        }
        #endregion
        #endregion

        #region Create Criteria
        protected virtual void CreateCriteria(Entity entity, CodeTypeDeclaration declaration)
		{
            CreateRelationshipCriteria(entity, declaration);
            CreateFetchAllCriteria(entity, declaration);
		}

		#region Create RelationshipCriteria
		protected virtual void CreateRelationshipCriteria(Entity entity, CodeTypeDeclaration declaration)
		{
			foreach (EntityRelationship relationship in entity.Relationships)
			{
                if (relationship.With == entity && relationship.Type == EntityRelationshipType.Child)
				{
                    IParentRelationship parent = Dependency.GetContract<IParentRelationship>(relationship.From);
                    if (parent != null)
                    {
                        CodeTypeDeclaration criteria = parent.FetchByParentCriteria(relationship);
                        declaration.Members.Add(criteria);
                    }
                    else
                    {
                        OnWarning(new ESharpBuildWarning(
                            ESharpErrorCode.ES0004,
                            entity.Elements[0]));
                    }
				}
			}
		} 
		#endregion
		#region CreateFetchAllCriteria
        protected virtual void CreateFetchAllCriteria(Entity entity, CodeTypeDeclaration declaration)
		{
			CodeTypeDeclaration d = new CodeTypeDeclaration();
			d.Name = "FetchAllCriteria";
			d.TypeAttributes = System.Reflection.TypeAttributes.NestedAssembly;
			d.BaseTypes.Add(new CodeTypeReference(typeof(CriteriaBase)));
			d.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, d.Name));
			d.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
			declaration.Members.Add(d);

            CodeConstructor c1 = new CodeConstructor();
            c1.Attributes = MemberAttributes.Public;
            d.Members.Add(c1);

            CodeConstructor c2 = new CodeConstructor();
            c2.Attributes = MemberAttributes.Public;
            c2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "startIndex"));
            c2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "maxRows"));
            c2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "fieldOrder"));
            c2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(SortOrder), "sortOrder"));

            c2.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("startIndex"));
            c2.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("maxRows"));
            c2.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("fieldOrder"));
            c2.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("sortOrder"));

            d.Members.Add(c2);
		} 
		#endregion
		#endregion

		#region Create DataAccess
        protected virtual void CreateDataAccess(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Attributes = MemberAttributes.Family | MemberAttributes.Override;
            m.Name = "FetchEntityCollection";
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(DbConnection), "cn"));
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(CriteriaBase), "c"));
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeConditionStatement condition = CreateFetchAllCriteriaHandler(entity);
            m.Statements.Add(condition);

            condition = CreateRelationshipCriteriaHandlers(entity, condition);
            condition = CreateQueryCriteriaHandler(entity, condition);
        }

        protected virtual CodeConditionStatement CreateFetchAllCriteriaHandler(Entity entity)
        {
            CodeVariableReferenceExpression criteriaReference = new CodeVariableReferenceExpression("c");
            CodeMethodInvokeExpression isAssignableFromFetchAll = new CodeMethodInvokeExpression(
                new CodeTypeOfExpression("FetchAllCriteria"),
                "IsAssignableFrom",
                new CodeMethodInvokeExpression(
                    criteriaReference,
                    "GetType"));

            CodeMethodInvokeExpression fetchAll = new CodeMethodInvokeExpression(
                GetProvider(),
                "FetchCollection",
                new CodeVariableReferenceExpression("cn"),
                new CodePrimitiveExpression(entity.Name + "CollectionFetchAll"),
                criteriaReference,
                new CodeDelegateCreateExpression(
                    new CodeTypeReference(typeof(EventHandler<LoadEventArgs>)),
                    new CodeThisReferenceExpression(),
                    "Load"),
                new CodePropertyReferenceExpression(criteriaReference, "StartIndex"),
                new CodePropertyReferenceExpression(criteriaReference, "MaxEntities"),
                new CodePropertyReferenceExpression(criteriaReference, "SortField"),
                new CodePropertyReferenceExpression(criteriaReference, "SortOrder"));

            CodeAssignStatement totalEntities = new CodeAssignStatement(
                new CodePropertyReferenceExpression(criteriaReference, "TotalEntities"),
                fetchAll);

            CodeConditionStatement c1 = new CodeConditionStatement(
                isAssignableFromFetchAll,
                totalEntities);

            return c1;
        }

        /// <summary>
        /// To chain conditions you need to add a condition to the previous conditions FalseStatements. 
        /// This method will always be called with a previous ifCondition being passed in.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="ifCondition"></param>
        /// <returns>The last condition in the chain to continue chaining if desired.</returns>
        protected virtual CodeConditionStatement CreateRelationshipCriteriaHandlers(Entity entity, CodeConditionStatement ifCondition)
        {
            CodeConditionStatement last = ifCondition;
            foreach (EntityRelationship relationship in entity.Relationships)
            {
                if (relationship.With == entity && relationship.Type == EntityRelationshipType.Child)
                {
                    // Generate fetch by parent
                    IParentRelationship contract = Dependency.GetContract<IParentRelationship>(relationship.With);
                    if (contract != null)
                    {
                        CodeConditionStatement c2 = contract.FetchByParentCollection(relationship, new CodeVariableReferenceExpression("c"));
                        ifCondition.FalseStatements.Add(c2);
                        ifCondition = c2;
                        last = ifCondition;
                    }
                    else
                    {
                        OnWarning(new ESharpBuildWarning(
                            ESharpErrorCode.ES0004,
                            relationship.Elements[0]));
                    }
                }
            }
            return last;
        }

        /// <summary>
        /// Add the condition to the FalseStatements of the condition argument.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="condition">The last condition in the chain</param>
        /// <returns>The last condition in the chain</returns>
        private CodeConditionStatement CreateQueryCriteriaHandler(Entity entity, CodeConditionStatement condition)
        {
            CodeVariableReferenceExpression criteriaReference = new CodeVariableReferenceExpression("c");
            CodeMethodInvokeExpression isAssignableFromQuery = new CodeMethodInvokeExpression(
                new CodeTypeOfExpression(new CodeTypeReference(typeof(QueryCriteria))),
                "IsAssignableFrom",
                new CodeMethodInvokeExpression(
                    criteriaReference,
                    "GetType"));

            CodeMethodInvokeExpression query = new CodeMethodInvokeExpression(
                GetProvider(),
                "Query",
                new CodeVariableReferenceExpression("cn"),
                new CodeCastExpression(typeof(QueryCriteria), criteriaReference),
                new CodeDelegateCreateExpression(
                    new CodeTypeReference(typeof(EventHandler<LoadEventArgs>)),
                    new CodeThisReferenceExpression(),
                    "Load"));

            CodeConditionStatement c1 = new CodeConditionStatement(isAssignableFromQuery);
            c1.TrueStatements.Add(query);

            condition.FalseStatements.Add(c1);

            return c1;
        }
		#endregion

        #region Create Load
        protected virtual void CreateLoad(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Attributes = MemberAttributes.Override | MemberAttributes.Family;
            m.Name = "LoadEntityCollection";
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender"));
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(LoadEventArgs), "e"));
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Load"));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeVariableReferenceExpression e = new CodeVariableReferenceExpression("e");
            CodePropertyReferenceExpression reader = new CodePropertyReferenceExpression(e, "Reader");
            CodePropertyReferenceExpression criteria = new CodePropertyReferenceExpression(e, "Criteria");

            CodeIterationStatement i = new CodeIterationStatement(
                new CodeSnippetStatement(""),
                new CodeMethodInvokeExpression(
                    reader,
                    "Read"),
                new CodeSnippetStatement(""));

            CodeMethodInvokeExpression add = new CodeMethodInvokeExpression(
                null,
                "Add",
                new CodeObjectCreateExpression(
                    entity.Name,
                    reader,
                    criteria));

            i.Statements.Add(add);
            m.Statements.Add(i);
        }
        #endregion

        #region Create ToInfoArray
        protected virtual void CreateToInfoArray(Entity entity, CodeTypeDeclaration declaration)
        {
            IInfoObject toInfoArray = Dependency.GetContract<IInfoObject>(entity);
            if(toInfoArray!=null)
            {
                CodeMemberMethod m = toInfoArray.ToInfoArrayMethod(entity);
                declaration.Members.Add(m);
            }
            else
            {
                EntityElement d = entity.Elements.Find(new Predicate<EntityElement>(delegate(EntityElement el)
               {
                   return el.Type == EntityElementType.EntityDeclaration;
               }));

                OnWarning(new ESharpBuildWarning(
                    ESharpErrorCode.ES0008,
                    d));
            }
        }
        #endregion

        #region IChildRelationshipMembers Members

        public CodeTypeMember[] ChildRelationshipMembers(EntityRelationship relationship)
        {
            CodeMemberField f = new CodeMemberField();
            f.Name = "_" + relationship.Name;
            f.Type = new CodeTypeReference(relationship.With.Name + "Collection");
            //f.InitExpression = new CodePrimitiveExpression(null);
            f.Attributes = MemberAttributes.Private;
            f.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Relationship " + relationship.Name));

            CodeMemberProperty p = new CodeMemberProperty();
            p.Name = relationship.Name;
            p.Type = new CodeTypeReference(relationship.With.Name + "Collection");
            p.Attributes = MemberAttributes.Public;
            p.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            p.HasGet = true;
            p.HasSet = false;

            CodeConditionStatement ccs = new CodeConditionStatement(
                new CodeBinaryOperatorExpression(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_" + relationship.Name),
                    CodeBinaryOperatorType.IdentityEquality,
                    new CodePrimitiveExpression(null)));

            CodeMethodInvokeExpression m = new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression(relationship.With.Name + "Collection"),
                "FetchBy" + relationship.From.Name);

            foreach (EntityField fld in relationship.On.Keys)
            {
                m.Parameters.Add(new CodeVariableReferenceExpression(fld.Name));
            }
            CodeAssignStatement assign = new CodeAssignStatement(
                new CodeFieldReferenceExpression(null, "_" + relationship.Name),
                m);
            ccs.TrueStatements.Add(assign);

            p.GetStatements.Add(ccs);

            //Return the relationship object
            p.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(null, "_" + relationship.Name)));
            
            List<CodeTypeMember> members = new List<CodeTypeMember>();

            foreach (EntityRelationship back in relationship.With.Relationships)
            {
                if (back.From == relationship.With && back.With == relationship.From)
                {
                    ccs.TrueStatements.Add(new CodeAttachEventStatement(
                        new CodeFieldReferenceExpression(null, "_" + relationship.Name),
                        "Added",
                        new CodeDelegateCreateExpression(
                            new CodeTypeReference("EventHandler",
                                new CodeTypeReference("EventArgs",
                                    new CodeTypeReference(back.From.Name))),
                            new CodeThisReferenceExpression(), 
                            relationship.Name + "Added")));

                    ccs.TrueStatements.Add(new CodeAttachEventStatement(
                        new CodeFieldReferenceExpression(null, "_" + relationship.Name),
                        "Removed",
                        new CodeDelegateCreateExpression(
                            new CodeTypeReference("EventHandler",
                                new CodeTypeReference("EventArgs",
                                    new CodeTypeReference(back.From.Name))),
                            new CodeThisReferenceExpression(), 
                            relationship.Name + "Removed")));

                    CodeMemberMethod added = new CodeMemberMethod();
                    added.Attributes = MemberAttributes.Private;
                    added.Name = relationship.Name + "Added";
                    added.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, relationship.Name + " Added / Removed"));
                    added.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender"));
                    added.Parameters.Add(new CodeParameterDeclarationExpression(
                        new CodeTypeReference("EventArgs", new CodeTypeReference(back.From.Name)),
                        "e"));
                    added.Statements.Add(
                        new CodeAssignStatement(
                            new CodePropertyReferenceExpression(
                                new CodePropertyReferenceExpression(
                                    new CodeVariableReferenceExpression("e"),
                                    "Arg"),
                                back.Name),
                            new CodeThisReferenceExpression()));
                     
                    members.Add(added);

                    CodeMemberMethod removed = new CodeMemberMethod();
                    removed.Attributes = MemberAttributes.Private;
                    removed.Name = relationship.Name + "Removed";
                    removed.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
                    removed.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender"));
                    removed.Parameters.Add(new CodeParameterDeclarationExpression(
                        new CodeTypeReference(
                            "EventArgs", 
                            new CodeTypeReference(back.From.Name)), 
                        "e"));

                    removed.Statements.Add(new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            new CodePropertyReferenceExpression(
                                new CodeVariableReferenceExpression("e"),
                                "Arg"),
                            back.Name),
                        new CodePrimitiveExpression(null)));

                    members.Add(removed);

                    break;
                }
            }
            members.Add(f);
            members.Add(p);

            return members.ToArray();
        }
        
        public CodeExpression[] DeleteChildrenExpressions(EntityRelationship relationship)
        {
            CodeMethodInvokeExpression clear = new CodeMethodInvokeExpression(
                        new CodePropertyReferenceExpression(null, relationship.Name),
                        "Clear");

            CodeMethodInvokeExpression persist = new CodeMethodInvokeExpression(
                new CodePropertyReferenceExpression(null, relationship.Name),
                "Persist",
                new CodePropertyReferenceExpression(
                    new CodeVariableReferenceExpression("e"),
                    "Transaction"),
                new CodeFieldReferenceExpression(
                    new CodeTypeReferenceExpression(typeof(PersistType)),
                    "Downward"));

            return new CodeExpression[] { clear, persist };
        }

        public CodeStatement PersistChildrenStatement(EntityRelationship relationship)
        {
            CodeFieldReferenceExpression field = new CodeFieldReferenceExpression(null, "_" + relationship.Name);

            CodeConditionStatement ccs = new CodeConditionStatement(
                new CodeBinaryOperatorExpression(
                    field,
                     CodeBinaryOperatorType.IdentityInequality,
                     new CodePrimitiveExpression(null)));

            ccs.TrueStatements.Add(new CodeMethodInvokeExpression(
                field,
                "Persist",
                new CodePropertyReferenceExpression(
                    new CodeVariableReferenceExpression("e"),
                    "Transaction"),
                new CodeFieldReferenceExpression(
                    new CodeTypeReferenceExpression(typeof(PersistType)),
                    "Downward")));

            return ccs;
        }

        public CodeTypeOfExpression TypeOfChild(Entity entity)
        {
            return new CodeTypeOfExpression(entity.Name + "Collection");
        }

        public CodeStatement BackRelationshipAdd(EntityRelationship relationship, EntityRelationship backRelationship, out CodeTypeMember[] members)
        {
            List<CodeTypeMember> memberList = new List<CodeTypeMember>();
                
            CodeMemberField addingParent = new CodeMemberField(typeof(bool), "_adding" + relationship.With);
            addingParent.CustomAttributes.Add(new CodeAttributeDeclaration(
                new CodeTypeReference(typeof(NonSerializedAttribute))));
            memberList.Add(addingParent);

            CodeFieldReferenceExpression addingParentReference = new CodeFieldReferenceExpression(null, "_adding" + relationship.With);

            CodeConditionStatement ifNotAdding = new CodeConditionStatement(
                new CodeBinaryOperatorExpression(
                    addingParentReference,
                    CodeBinaryOperatorType.IdentityInequality,
                    new CodePrimitiveExpression(true)));

            CodeConditionStatement ifNotContains = new CodeConditionStatement();
            ifNotContains.Condition = new CodeBinaryOperatorExpression(
                new CodeMethodInvokeExpression(
                    new CodePropertyReferenceExpression(
                        new CodeFieldReferenceExpression(
                            new CodeThisReferenceExpression(),
                            "_" + relationship.Name),
                        backRelationship.Name),
                        "Contains",
                        new CodeThisReferenceExpression()),
                 CodeBinaryOperatorType.IdentityInequality,
                 new CodePrimitiveExpression(true));

            CodeAssignStatement startAdding = new CodeAssignStatement(
                addingParentReference,
                new CodePrimitiveExpression(true));
            CodeAssignStatement endAdding = new CodeAssignStatement(
                addingParentReference,
                new CodePrimitiveExpression(false));

            CodeMethodInvokeExpression add = new CodeMethodInvokeExpression(
                new CodePropertyReferenceExpression(
                    new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(),
                        "_" + relationship.Name),
                    backRelationship.Name),
                    "Add",
                    new CodeThisReferenceExpression());

            ifNotAdding.TrueStatements.Add(ifNotContains);
            ifNotContains.TrueStatements.Add(startAdding);
            ifNotContains.TrueStatements.Add(add);
            ifNotContains.TrueStatements.Add(endAdding);

            members = memberList.ToArray();
            return ifNotAdding;
        }

        #endregion

        #region Helpers
        private CodeExpression GetProvider()
        {
            return new CodeCastExpression(typeof(IDatabaseProvider),
                new CodeMethodInvokeExpression(
                    new CodePropertyReferenceExpression(
                        new CodeTypeReferenceExpression(typeof(Settings)),
                        "Instance"),
                    "GetSetting",
                    new CodeFieldReferenceExpression(
                        new CodeTypeReferenceExpression(typeof(SettingsType)),
                        "Provider"),
                    new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "GetType")));
        }
        #endregion
    }
}
